﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;

namespace Servico
{
    public class Escalonador
    {
        private Dictionary<int, Pedido> pedidos;
        private Dictionary<int, Tecnico> tecnicos;
        private Dictionary<int, Dictionary<DateTime, Tarefa>> alocacoes_temporarias;

        //Tive de colocar estas variáveis aqui por causa dos warnings
        private HashSet<Pedido> lista_pedidos_adiados = new HashSet<Pedido>();
        private HashSet<Pedido> lista_pedidos_P1 = new HashSet<Pedido>();
        private HashSet<Pedido> lista_pedidos_P2 = new HashSet<Pedido>();
        private HashSet<Pedido> lista_pedidos_P3 = new HashSet<Pedido>();
        private Dictionary<DateTime, Tarefa> planeamento = new Dictionary<DateTime, Tarefa>();
        private DateTime hora_fim = new DateTime(2011, 5, 25, 18, 0, 0);
        private DateTime hora_inicio = new DateTime(2011, 5, 25, 9, 0, 0);

        public Escalonador()
        {
            pedidos = new Dictionary<int, Pedido>();
            tecnicos = new Dictionary<int, Tecnico>();
            alocacoes_temporarias = new Dictionary<int, Dictionary<DateTime, Tarefa>>();
        }

        public Escalonador(Dictionary<int, Pedido> pedidos, Dictionary<int, Tecnico> tecnicos, Dictionary<int, Dictionary<DateTime, Tarefa>> alocacoes_temporarias)
        {
            this.pedidos = pedidos;
            this.tecnicos = tecnicos;
            this.alocacoes_temporarias = alocacoes_temporarias;
        }

        public string init()
        {
            tecnicos = carregarTecnicos();
            pedidos = carregarPedidos();
            //foreach (Pedido pedido in pedidos.Values)
            //{
            //System.Console.WriteLine("pedido: " + pedido.getId_pedido_recebido() + "\n");
            //System.Console.Read();
            //foreach (Tarefa tarefa in pedido.getTarefas().Values)
            //{
            //System.Console.WriteLine("tarefa " + tarefa.getId() + "\n");
            //    System.Console.Read();
            //}
            //}
            //System.Console.Read();
            //int i = 0;
            //StringBuilder aux = new StringBuilder();
            //DateTime hora_actual = DateTime.Now;
            foreach (Tecnico tecnico in tecnicos.Values)
            {
                //aux.Append(i.ToString() + ", ");
                alocacoes_temporarias.Add(tecnico.getId(), new Dictionary<DateTime, Tarefa>());
                //if (alocacoes_temporarias[tecnico.getId()].ContainsKey(hora_actual)) aux.Append(i.ToString() + ", ");
                //i++;
            }
            //return (aux.ToString());
            /*StringBuilder auxtec = new StringBuilder();
            foreach (Tecnico tecnico in tecnicos.Values)
            {
                auxtec.Append("Tecnico " + tecnico.getPlaneamento().ElementAt(1).ToString() + "\n");
            }

            return (auxtec.ToString());

            StringBuilder auxped = new StringBuilder();
            foreach (Pedido pedido in pedidos.Values)
            {
                auxped.Append("Pedido " + pedido.getId().ToString() + "\n");
            }

            StringBuilder auxtar = new StringBuilder();
            foreach (Tarefa tarefa in pedidos[1].getTarefas().Values)
            {
                auxtar.Append("Tarefa " + tarefa.getId().ToString() + "\n");
            }

            return (auxtec.ToString() + auxped.ToString() + auxtar.ToString());
            //return (pedidos[1].getTarefas()[8].ToString() + "\n");*/
            /*DateTime now = DateTime.Now;
            Tarefa tar = new Tarefa();
            Dictionary<DateTime, Tarefa> aux = new Dictionary<DateTime, Tarefa>();
            aux.Add(now, tar);
            tecnicos[1].setPlaneamento(aux);*/
            string bla = escalonar();
            return bla;
        }


        public Dictionary<int, Tecnico> carregarTecnicos()
        {
            Dados.SMI smi = new Dados.SMI("192.168.2.79\\SQLEXPRESS", "STAFFSCHED", "LI4", "obelo");
            smi.connect();
            return (smi.getTecnicosBD());
        }


        public Dictionary<int, Pedido> carregarPedidos()
        {
            Dados.SMI smi = new Dados.SMI("192.168.2.79\\SQLEXPRESS", "STAFFSCHED", "LI4", "obelo");
            smi.connect();
            return (smi.getPedidosBD());
        }


        public void actualizarTecnicos()
        {
            foreach (int codigo_tecnico in alocacoes_temporarias.Keys)
            {
                //Tecnico t;
                //tecnicos.TryGetValue(codigo_tecnico, out t);
                //tecnicos[codigo_tecnico].getPlaneamento();     ->planeamento do tecnico

                //Dictionary<DateTime, Tarefa> alocTemp;
                //alocacoes_temporarias.TryGetValue(codigo_tecnico, out alocTemp);
                //alocacoes_temporarias[codigo_tecnico];        -> planeamento temporario


                tecnicos[codigo_tecnico].setPlaneamento(alocacoes_temporarias[codigo_tecnico]);

                //foreach(DateTime date in alocacoes_temporarias[codigo_tecnico].Keys)
                //{
                //    tecnicos[codigo_tecnico].getPlaneamento()
                //}

                /*foreach(DateTime dt in alocTemp.Keys){
                    Tarefa tar;
                    alocTemp.TryGetValue(dt, out tar);
                    //Dictionary<DateTime, Tarefa> aux = new Dictionary<DateTime, Tarefa>();
                    planeamento = t.getPlaneamento();
                    planeamento.Add(dt, tar);//aux.Add(dt, tar);
                    t.setPlaneamento(planeamento);*/

                // t.getPlaneamento().Add(dt, tar);
                //}
            }
        }


        public void libertarAlocacoesTemporarias()
        {
            alocacoes_temporarias = new Dictionary<int,Dictionary<DateTime,Tarefa>>();

            foreach (Tecnico tecnico in tecnicos.Values)
            {
                alocacoes_temporarias.Add(tecnico.getId(), new Dictionary<DateTime, Tarefa>());
            }
            //foreach (Tecnico tecnico in tecnicos.Values)
            //{
                //alocacoes_temporarias.Add(tecnico.getId(), new Dictionary<DateTime, Tarefa>());
            //}
        }

        //TODO analisar o nivel da tarefa e o nivel do funcionario
        bool alocarTarefa(Tarefa tarefa, DateTime hora_actual, int id_cliente_associado)
        {
            int nivel = tarefa.getNivel_minimo();
            foreach (Tecnico tecnico in tecnicos.Values)
            {
                // se o tecnico tiver qualificacoes suficientes e puder alocar a tarefa nessa hora
                if ((tecnico.getNivel() == nivel) && (!(alocacoes_temporarias[tecnico.getId()].ContainsKey(hora_actual))) && (!(tecnico.getPlaneamento().ContainsKey(hora_actual))))
                {
                    tarefa.setId_cliente_associado(id_cliente_associado);
                    /*if (alocacoes_temporarias.ContainsKey(tecnico.getId())){
                        Dictionary<DateTime, Tarefa> aux1 = new Dictionary<DateTime,Tarefa>();
                        alocacoes_temporarias.TryGetValue(tecnico.getId(), out aux1);
                        aux1.Add(hora_actual, tarefa);
                        //alocacoes_temporarias.     VER ISTO CARAGO!
                    }*/
                    alocacoes_temporarias[tecnico.getId()].Add(hora_actual, tarefa);
                    /*Dictionary<DateTime, Tarefa> aux;
                    alocacoes_temporarias.TryGetValue(tecnico.getId(), out aux); //POSSIVEL ERRO
                    aux.Add(hora_actual, tarefa);*/
                    return true;
                }
            }
            foreach (Tecnico tecnico in tecnicos.Values)
            {
                if ((tecnico.getNivel() == nivel + 1) && (!(alocacoes_temporarias[tecnico.getId()].ContainsKey(hora_actual))) && (!(tecnico.getPlaneamento().ContainsKey(hora_actual))))
                {
                    tarefa.setId_cliente_associado(id_cliente_associado);
                    /*if (alocacoes_temporarias.ContainsKey(tecnico.getId())){
                        Dictionary<DateTime, Tarefa> aux1 = new Dictionary<DateTime,Tarefa>();
                        alocacoes_temporarias.TryGetValue(tecnico.getId(), out aux1);
                        aux1.Add(hora_actual, tarefa);
                        //alocacoes_temporarias.     VER ISTO CARAGO!
                    }*/
                    alocacoes_temporarias[tecnico.getId()].Add(hora_actual, tarefa);
                    /*Dictionary<DateTime, Tarefa> aux;
                    alocacoes_temporarias.TryGetValue(tecnico.getId(), out aux); //POSSIVEL ERRO
                    aux.Add(hora_actual, tarefa);*/
                    return true;
                }
            }
            return false;
        }



        /**
        * #define hora_inicio 9h
        * #define hora_fim 18h
        * @param tarefa_actual = 0 no inicio
        * @return true se alocou pedido
        * @return false se nao alocou pedido
       **/


        public bool alocarPedido(int tarefa_actual, DateTime hora_actual, DateTime hora_primeira_tarefa, Pedido pedido, int num_tarefas)
        {
            int horaF = this.hora_fim.Hour;
            int horaA = hora_actual.Hour;
            int horaP = hora_primeira_tarefa.Hour;
            int duracao_pedido = pedido.duracaoPedido();
            System.Console.WriteLine("pedido: " + pedido.getId_pedido_recebido() + "; tarefa_actual: " + tarefa_actual);
            System.Console.Read();
            Tarefa tarefa = pedido.getTarefas()[tarefa_actual];
            int duracao_tarefa = tarefa.getDuracao();
            //if (pedido.getAlocado() == false)
            //{
            if (tarefa.getPronta())
            {
                num_tarefas--;
                if (num_tarefas <= 0)
                {
                    return true;
                }
                //hora_actual = hora_actual.AddHours(tarefa.getDuracao());
                tarefa_actual++;
                return alocarPedido(tarefa_actual, hora_actual, hora_primeira_tarefa, pedido, num_tarefas);
            }
            else
            {
                if (horaP + duracao_pedido <= horaF) //tarefa && (horaA + duracao_pedido <= horaF)
                {
                    if (alocarTarefa(tarefa, hora_actual, tarefa.getId_cliente_associado()))
                    {
                        num_tarefas--;
                        if (num_tarefas <= 0)
                        {
                            return true;
                        }
                        hora_actual = hora_actual.AddHours(tarefa.getDuracao());
                        tarefa_actual++;
                        return alocarPedido(tarefa_actual, hora_actual, hora_primeira_tarefa, pedido, num_tarefas);
                    }
                    else
                    {
                        libertarAlocacoesTemporarias();
                        hora_primeira_tarefa = hora_primeira_tarefa.AddHours(1);
                        return alocarPedido(pedido.getTarefas().Keys.Min(), hora_primeira_tarefa, hora_primeira_tarefa, pedido, pedido.getTarefas().Count());
                    }
                }
            }
            //}
            if ((horaP + duracao_pedido) > horaF)
            {
                libertarAlocacoesTemporarias();
                return false;
            }
            return true;
        }


        public void alocarPedidos(HashSet<Pedido> lista_pedidos)
        {
            foreach (Pedido pedido in lista_pedidos)
            {
                //if (pedido.getAlocado() == true) continue;
                if (alocarPedido(pedido.getTarefas().Keys.Min(), hora_inicio, hora_inicio, pedido, pedido.getNumTarefas()) == true)
                {
                    actualizarTecnicos();
                    libertarAlocacoesTemporarias();
                    pedido.setAlocado(true);
                }
                else aumentaPrioridade(pedido);
            }

        }


        public void aumentaPrioridade(Pedido p)
        {
            int prior = p.getPrioridade();

            if (prior > 1) p.setPrioridade(prior--);
        }


        public String escalonar()
        {
            foreach (Pedido pedido in pedidos.Values)
            {
                if (pedido.getAlocado() == true)
                    lista_pedidos_adiados.Add(pedido);
                else
                    if (pedido.getPrioridade() == 1)
                        lista_pedidos_P1.Add(pedido);
                    else
                        if (pedido.getPrioridade() == 2)
                            lista_pedidos_P2.Add(pedido);
                        else
                            if (pedido.getPrioridade() == 3)
                                lista_pedidos_P3.Add(pedido);
            }

            if (lista_pedidos_adiados.Count() != 0) alocarPedidos(lista_pedidos_adiados);
            if (lista_pedidos_P1.Count() != 0) alocarPedidos(lista_pedidos_P1);
            if (lista_pedidos_P2.Count() != 0) alocarPedidos(lista_pedidos_P2);
            if (lista_pedidos_P3.Count() != 0) alocarPedidos(lista_pedidos_P3);
            regPedidos();
            String pg = gerarPlaneamentoGeralXML();
            return pg;
        }

        public void regPedidos()
        {
            //string teste;
            Dados.SMI smi = new Dados.SMI("192.168.2.79\\SQLEXPRESS", "STAFFSCHED", "LI4", "obelo");
            smi.connect();
            smi.registarPedidos(pedidos);
            //return teste;
        }

        public String gerarPlaneamentoGeralXML()
        {
            Dados.SMI smi = new Dados.SMI("192.168.2.79\\SQLEXPRESS", "STAFFSCHED", "LI4", "obelo");
            smi.connect();
            StringBuilder planeamento = new StringBuilder();
            planeamento.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            planeamento.Append("<PlaneamentoGeral>");

            foreach (Tecnico tecnico in tecnicos.Values)
            {
                //if (tecnico.getPlaneamento() == null) return ("erro\n");
                //return (tecnico.getPlaneamento().Count.ToString() + "\n");
                planeamento.Append("<Tecnico id='" + tecnico.getId() + "'><Nome>" + smi.getNomeTecnico(tecnico) + "</Nome>");
                planeamento.Append("<Tarefas>");

                foreach (Tarefa t in tecnico.getPlaneamento().Values)
                {
                    planeamento.Append("<Tarefa id='" + t.getId() + "'>");
                    planeamento.Append("<idPedidoRecebido>" + t.getId_pedido_recebido() + "</idPedidoRecebido>");
                    planeamento.Append("<NomeCliente>" + smi.getNomeCliente(t) + "</NomeCliente>");
                    planeamento.Append("<HoraInicialTarefa>" + tecnico.getHoraInicioTarefa(t) + "</HoraInicialTarefa>");
                    planeamento.Append("<HoraFimTarefa>" + tecnico.getHoraFimTarefa(t) + "</HoraFimTarefa>");
                    planeamento.Append("<HoraInicioPedido>" + pedidos[t.getId_pedido_recebido()].getInicioPedido(t.getId(), t.getId_pedido_recebido(), tecnicos) + "</HoraInicioPedido>");
                    planeamento.Append("<HoraFimPedido>" + pedidos[t.getId_pedido_recebido()].getFimPedido(t.getId(), t.getId_pedido_recebido(), tecnicos, pedidos) + "</HoraFimPedido>");
                    planeamento.Append("<DesignacaoTarefa>" + smi.getDesignacaoTarefa(t) + "</DesignacaoTarefa>");
                    planeamento.Append("<DesignacaoPedido>" + smi.getDesignacaoPedido(t) + "</DesignacaoPedido>");
                    planeamento.Append("<Morada>" + smi.getMoradaCliente(t) + "</Morada>");
                    planeamento.Append("</Tarefa>");

                }

                planeamento.Append("</Tarefas>");
                planeamento.Append("</Tecnico>");
            }
            planeamento.Append("</PlaneamentoGeral>");
            return planeamento.ToString();
        }




        public bool registarPlaneamentoGeral()
        {
            String pg = gerarPlaneamentoGeralXML();
            Dados.SMI smi = new Dados.SMI("192.168.2.79\\SQLEXPRESS", "STAFFSCHED", "LI4", "obelo");
            smi.connect();
            if (smi.registarPlaneamentoGeral(pg) == true) { return true; }
            else { return false; }
        }


        /*public static void Main(string[] args)
        {
            try
            {

                Dados.SMI smi = new Dados.SMI("192.168.2.79\\SQLEXPRESS", "STAFFSCHED", "LI4", "obelo");
                smi.connect();
                Escalonador escalonador = new Escalonador();
                String teste = escalonador.init();
                smi.disconnect();
                
            }
            catch (Exception e)
            {
                
            }

        }*/
    }


    //TODO retirar o campo horas ao datetime do consultarPlaneamentoGeral, porque ninguem sabe a hora ao certo, é so a data
}